<template>
  <div v-if="easyFlowVisible" style="height: calc(100vh);">
    <el-row>
      <!--顶部工具菜单-->
      <el-col :span="24">
        <div class="ef-tooltar">
          <el-link type="primary" :underline="false">{{ data.name }}</el-link>
          <el-divider direction="vertical"></el-divider>
          <el-button type="text" icon="el-icon-delete" size="large" @click="deleteElement"
                     :disabled="!this.activeElement.type"></el-button>
          <el-divider direction="vertical"></el-divider>
          <el-button type="text" icon="el-icon-download" size="large" @click="downloadData"></el-button>
          <el-divider direction="vertical"></el-divider>
          <el-button type="text" icon="el-icon-plus" size="large" @click="zoomAdd"></el-button>
          <el-divider direction="vertical"></el-divider>
          <el-button type="text" icon="el-icon-minus" size="large" @click="zoomSub"></el-button>
          <div style="float: right;margin-right: 5px">
            <el-button type="info" plain round icon="el-icon-document" @click="dataInfo" size="mini">流程信息</el-button>
            <el-button type="primary" plain round @click="dataReloadA" icon="el-icon-refresh" size="mini">切换流程A
            </el-button>
            <el-button type="primary" plain round @click="dataReloadB" icon="el-icon-refresh" size="mini">切换流程B
            </el-button>
            <el-button type="primary" plain round @click="dataReloadC" icon="el-icon-refresh" size="mini">切换流程C
            </el-button>
            <el-button type="primary" plain round @click="dataReloadD" icon="el-icon-refresh" size="mini">自定义样式
            </el-button>
            <el-button type="primary" plain round @click="dataReloadE" icon="el-icon-refresh" size="mini">力导图
            </el-button>
            <el-button type="info" plain round icon="el-icon-document" @click="openHelp" size="mini">帮助</el-button>
          </div>
        </div>
      </el-col>
    </el-row>
    <div style="display: flex;height: calc(100% - 47px);">
      <div style="width: 230px;border-right: 1px solid #dce3e8;">
        <node-menu @addNode="addNode" ref="nodeMenu"></node-menu>
      </div>
      <div id="efContainer" ref="efContainer" class="container" v-flowDrag>
        <template v-for="node in data.nodeList">
          <flow-node
            :id="node.id"
            :key="node.id"
            :node="node"
            :activeElement="activeElement"
            @changeNodeSite="changeNodeSite"
            @nodeRightMenu="nodeRightMenu"
            @clickNode="clickNode"
          >
          </flow-node>
        </template>
        <!-- 给画布一个默认的宽度和高度 -->
        <div style="position:absolute;top: 2000px;left: 2000px;">&nbsp;</div>
      </div>
      <!-- 右侧表单 -->
      <div style="width: 300px;border-left: 1px solid #dce3e8;background-color: #FBFBFB">
        <flow-node-form ref="nodeForm" @setLineLabel="setLineLabel"
                        @repaintEverything="repaintEverything"></flow-node-form>
      </div>
    </div>
    <!-- 流程数据详情 -->
    <flow-info v-if="flowInfoVisible" ref="flowInfo" :data="data"></flow-info>
    <flow-help v-if="flowHelpVisible" ref="flowHelp"></flow-help>
  </div>

</template>

<script>
  import draggable from 'vuedraggable'
  // import { jsPlumb } from 'jsplumb'
  // 使用修改后的jsplumb
  import './jsplumb'
  import {easyFlowMixin} from '@/components/ef/mixins'
  import flowNode from '@/components/ef/node'
  import nodeMenu from '@/components/ef/node_menu'
  import FlowInfo from '@/components/ef/info'
  import FlowHelp from '@/components/ef/help'
  import FlowNodeForm from './node_form'
  import lodash from 'lodash'
  import {getDataA} from './data_A'
  import {getDataB} from './data_B'
  import {getDataC} from './data_C'
  import {getDataD} from './data_D'
  import {getDataE} from './data_E'
  import {ForceDirected} from './force-directed'

  export default {
    data () {
      return {
        // jsPlumb 实例
        jsPlumb: null,
        // 控制画布销毁
        easyFlowVisible: true,
        // 控制流程数据显示与隐藏
        flowInfoVisible: false,
        // 是否加载完毕标志位
        loadEasyFlowFinish: false,
        flowHelpVisible: false,
        // 数据
        data: {},
        // 激活的元素、可能是节点、可能是连线
        activeElement: {
          // 可选值 node 、line
          type: undefined,
          // 节点ID
          nodeId: undefined,
          // 连线ID
          sourceId: undefined,
          targetId: undefined
        },
        zoom: 0.5
      }
    },
    // 一些基础配置移动该文件中
    mixins: [easyFlowMixin],
    components: {
      draggable, flowNode, nodeMenu, FlowInfo, FlowNodeForm, FlowHelp
    },
    directives: {
      'flowDrag': {
        bind (el, binding, vnode, oldNode) {
          if (!binding) {
            return
          }
          el.onmousedown = (e) => {
            if (e.button == 2) {
              // 右键不管
              return
            }
            //  鼠标按下，计算当前原始距离可视区的高度
            let disX = e.clientX
            let disY = e.clientY
            el.style.cursor = 'move'

            document.onmousemove = function (e) {
              // 移动时禁止默认事件
              e.preventDefault()
              const left = e.clientX - disX
              disX = e.clientX
              el.scrollLeft += -left

              const top = e.clientY - disY
              disY = e.clientY
              el.scrollTop += -top
            }

            document.onmouseup = function (e) {
              el.style.cursor = 'auto'
              document.onmousemove = null
              document.onmouseup = null
            }
          }
        }
      }
    },
    mounted () {
      this.jsPlumb = jsPlumb.getInstance()
      this.$nextTick(() => {
        // 默认加载流程A的数据、在这里可以根据具体的业务返回符合流程数据格式的数据即可
        this.dataReload(getDataB())
      })
    },
    methods: {
      // 返回唯一标识
      getUUID () {
        return Math.random().toString(36).substr(3, 10)
      },
      getUUID2 (len, radix) {
        var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
        var uuid = [],
          i
        radix = radix || chars.length

        if (len) {
          // Compact form
          for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
        } else {
          // rfc4122, version 4 form
          var r

          // rfc4122 requires these characters
          uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
          uuid[14] = '4'

          // Fill in random data.  At i==19 set the high bits of clock sequence as
          // per rfc4122, sec. 4.1.5
          for (i = 0; i < 36; i++) {
            if (!uuid[i]) {
              r = 0 | Math.random() * 16
              uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]
            }
          }
        }

        return uuid.join('')
      },
      getUUID3(len){
        return Number(Math.random().toString().substr(3,len) + Date.now()).toString(36)
      },
      jsPlumbInit () {
        this.jsPlumb.ready(() => {
          // 导入默认配置
          this.jsPlumb.importDefaults(this.jsplumbSetting)
          // 会使整个jsPlumb立即重绘。
          this.jsPlumb.setSuspendDrawing(false, true)
          // 初始化节点
          this.loadEasyFlow()
          // 单点击了连接线, https://www.cnblogs.com/ysx215/p/7615677.html
          this.jsPlumb.bind('click', (conn, originalEvent) => {
            this.activeElement.type = 'line'
            this.activeElement.sourceId = conn.sourceId
            this.activeElement.targetId = conn.targetId
            this.$refs.nodeForm.lineInit({
              from: conn.sourceId,
              to: conn.targetId,
              label: conn.getLabel()
            })
          })
          // 连线
          this.jsPlumb.bind('connection', (evt) => {
            let from = evt.source.id
            let to = evt.target.id
            if (this.loadEasyFlowFinish) {
              this.data.lineList.push({from: from, to: to})
            }
          })

          // 删除连线回调
          this.jsPlumb.bind('connectionDetached', (evt) => {
            this.deleteLine(evt.sourceId, evt.targetId)
          })

          // 改变线的连接节点
          this.jsPlumb.bind('connectionMoved', (evt) => {
            this.changeLine(evt.originalSourceId, evt.originalTargetId)
          })

          // 连线右击
          this.jsPlumb.bind('contextmenu', (evt) => {
            console.log('contextmenu', evt)
          })

          // 连线
          this.jsPlumb.bind('beforeDrop', (evt) => {
            let from = evt.sourceId
            let to = evt.targetId
            if (from === to) {
              this.$message.error('节点不支持连接自己')
              return false
            }
            if (this.hasLine(from, to)) {
              this.$message.error('该关系已存在,不允许重复创建')
              return false
            }
            if (this.hashOppositeLine(from, to)) {
              this.$message.error('不支持两个节点之间连线回环')
              return false
            }
            this.$message.success('连接成功')
            return true
          })

          // beforeDetach
          this.jsPlumb.bind('beforeDetach', (evt) => {
            console.log('beforeDetach', evt)
          })
          this.jsPlumb.setContainer(this.$refs.efContainer)
        })
      },
      // 加载流程图
      loadEasyFlow () {
        // 初始化节点
        for (var i = 0; i < this.data.nodeList.length; i++) {
          let node = this.data.nodeList[i]
          // 设置源点，可以拖出线连接其他节点
          this.jsPlumb.makeSource(node.id, lodash.merge(this.jsplumbSourceOptions, {}))
          // // 设置目标点，其他源点拖出的线可以连接该节点
          this.jsPlumb.makeTarget(node.id, this.jsplumbTargetOptions)
          if (!node.viewOnly) {
            this.jsPlumb.draggable(node.id, {
              containment: 'parent',
              stop: function (el) {
                // 拖拽节点结束后的对调
                console.log('拖拽结束: ', el)
              }
            })
          }
        }
        // 初始化连线
        for (var i = 0; i < this.data.lineList.length; i++) {
          let line = this.data.lineList[i]
          var connParam = {
            source: line.from,
            target: line.to,
            label: line.label ? line.label : '',
            connector: line.connector ? line.connector : '',
            anchors: line.anchors ? line.anchors : undefined,
            paintStyle: line.paintStyle ? line.paintStyle : undefined,
          }
          this.jsPlumb.connect(connParam, this.jsplumbConnectOptions)
        }
        this.$nextTick(function () {
          this.loadEasyFlowFinish = true
        })
      },
      // 设置连线条件
      setLineLabel (from, to, label) {
        var conn = this.jsPlumb.getConnections({
          source: from,
          target: to
        })[0]
        if (!label || label === '') {
          conn.removeClass('flowLabel')
          conn.addClass('emptyFlowLabel')
        } else {
          conn.addClass('flowLabel')
        }
        conn.setLabel({
          label: label,
        })

        this.data.lineList.forEach(function (line) {
          if (line.from == from && line.to == to) {
            line.label = label
          }
        })

      },
      // 删除激活的元素
      deleteElement () {
        if (this.activeElement.type === 'node') {
          this.deleteNode(this.activeElement.nodeId)
        } else if (this.activeElement.type === 'line') {
          this.$confirm('确定删除所点击的线吗?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
          }).then(() => {
            var conn = this.jsPlumb.getConnections({
              source: this.activeElement.sourceId,
              target: this.activeElement.targetId
            })[0]
            this.jsPlumb.deleteConnection(conn)
          }).catch(() => {
          })
        }
      },
      // 删除线
      deleteLine (from, to) {
        this.data.lineList = this.data.lineList.filter(function (line) {
          if (line.from == from && line.to == to) {
            return false
          }
          return true
        })
      },
      // 改变连线
      changeLine (oldFrom, oldTo) {
        this.deleteLine(oldFrom, oldTo)
      },
      // 改变节点的位置
      changeNodeSite (data) {
        for (var i = 0; i < this.data.nodeList.length; i++) {
          let node = this.data.nodeList[i]
          if (node.id === data.nodeId) {
            node.left = data.left
            node.top = data.top
          }
        }
      },
      /**
       * 拖拽结束后添加新的节点
       * @param evt
       * @param nodeMenu 被添加的节点对象
       * @param mousePosition 鼠标拖拽结束的坐标
       */
      addNode (evt, nodeMenu, mousePosition) {
        var screenX = evt.originalEvent.clientX, screenY = evt.originalEvent.clientY
        let efContainer = this.$refs.efContainer
        var containerRect = efContainer.getBoundingClientRect()
        var left = screenX, top = screenY
        // 计算是否拖入到容器中
        if (left < containerRect.x || left > containerRect.width + containerRect.x || top < containerRect.y || containerRect.y > containerRect.y + containerRect.height) {
          this.$message.error('请把节点拖入到画布中')
          return
        }
        left = left - containerRect.x + efContainer.scrollLeft
        top = top - containerRect.y + efContainer.scrollTop
        // 居中
        left -= 85
        top -= 16
        // var nodeId = this.getUUID2(20, 36)
        var nodeId = this.getUUID3(20)
        // 动态生成名字
        var origName = nodeMenu.name
        var nodeName = origName
        var index = 1
        while (index < 10000) {
          var repeat = false
          for (var i = 0; i < this.data.nodeList.length; i++) {
            let node = this.data.nodeList[i]
            if (node.name === nodeName) {
              nodeName = origName + index
              repeat = true
            }
          }
          if (repeat) {
            index++
            continue
          }
          break
        }
        var node = {
          id: nodeId,
          name: nodeName,
          type: nodeMenu.type,
          left: left + 'px',
          top: top + 'px',
          ico: nodeMenu.ico,
          state: 'success'
        }
        /**
         * 这里可以进行业务判断、是否能够添加该节点
         */
        this.data.nodeList.push(node)
        this.$nextTick(function () {
          this.jsPlumb.makeSource(nodeId, this.jsplumbSourceOptions)
          this.jsPlumb.makeTarget(nodeId, this.jsplumbTargetOptions)
          this.jsPlumb.draggable(nodeId, {
            containment: 'parent',
            stop: function (el) {
              // 拖拽节点结束后的对调
              console.log('拖拽结束: ', el)
            }
          })
        })
      },
      /**
       * 删除节点
       * @param nodeId 被删除节点的ID
       */
      deleteNode (nodeId) {
        this.$confirm('确定要删除节点' + nodeId + '?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
          closeOnClickModal: false
        }).then(() => {
          /**
           * 这里需要进行业务判断，是否可以删除
           */
          this.data.nodeList = this.data.nodeList.filter(function (node) {
            if (node.id === nodeId) {
              // 伪删除，将节点隐藏，否则会导致位置错位
              // node.show = false
              return false
            }
            return true
          })
          this.$nextTick(function () {
            this.jsPlumb.removeAllEndpoints(nodeId)
          })
        }).catch(() => {
        })
        return true
      },
      clickNode (nodeId) {
        this.activeElement.type = 'node'
        this.activeElement.nodeId = nodeId
        this.$refs.nodeForm.nodeInit(this.data, nodeId)
      },
      // 是否具有该线
      hasLine (from, to) {
        for (var i = 0; i < this.data.lineList.length; i++) {
          var line = this.data.lineList[i]
          if (line.from === from && line.to === to) {
            return true
          }
        }
        return false
      },
      // 是否含有相反的线
      hashOppositeLine (from, to) {
        return this.hasLine(to, from)
      },
      nodeRightMenu (nodeId, evt) {
        this.menu.show = true
        this.menu.curNodeId = nodeId
        this.menu.left = evt.x + 'px'
        this.menu.top = evt.y + 'px'
      },
      repaintEverything () {
        this.jsPlumb.repaint()
      },
      // 流程数据信息
      dataInfo () {
        this.flowInfoVisible = true
        this.$nextTick(function () {
          this.$refs.flowInfo.init()
        })
      },
      // 加载流程图
      dataReload (data) {
        this.easyFlowVisible = false
        this.data.nodeList = []
        this.data.lineList = []
        this.$nextTick(() => {
          data = lodash.cloneDeep(data)
          this.easyFlowVisible = true
          this.data = data
          this.$nextTick(() => {
            this.jsPlumb = jsPlumb.getInstance()
            this.$nextTick(() => {
              this.jsPlumbInit()
            })
          })
        })
      },
      // 模拟载入数据dataA
      dataReloadA () {
        this.dataReload(getDataA())
      },
      // 模拟载入数据dataB
      dataReloadB () {
        this.dataReload(getDataB())
      },
      // 模拟载入数据dataC
      dataReloadC () {
        this.dataReload(getDataC())
      },
      // 模拟载入数据dataD
      dataReloadD () {
        this.dataReload(getDataD())
      },
      // 模拟加载数据dataE，自适应创建坐标
      dataReloadE () {
        let dataE = getDataE()
        let tempData = lodash.cloneDeep(dataE)
        let data = ForceDirected(tempData)
        this.dataReload(data)
        this.$message({
          message: '力导图每次产生的布局是不一样的',
          type: 'warning'
        })
      },
      zoomAdd () {
        if (this.zoom >= 1) {
          return
        }
        this.zoom = this.zoom + 0.1
        this.$refs.efContainer.style.transform = `scale(${this.zoom})`
        this.jsPlumb.setZoom(this.zoom)
      },
      zoomSub () {
        if (this.zoom <= 0) {
          return
        }
        this.zoom = this.zoom - 0.1
        this.$refs.efContainer.style.transform = `scale(${this.zoom})`
        this.jsPlumb.setZoom(this.zoom)
      },
      // 下载数据
      downloadData () {
        this.$confirm('确定要下载该流程数据吗？', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
          closeOnClickModal: false
        }).then(() => {
          var datastr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(this.data, null, '\t'))
          var downloadAnchorNode = document.createElement('a')
          downloadAnchorNode.setAttribute('href', datastr)
          downloadAnchorNode.setAttribute('download', 'data.json')
          downloadAnchorNode.click()
          downloadAnchorNode.remove()
          this.$message.success('正在下载中,请稍后...')
        }).catch(() => {
        })
      },
      openHelp () {
        this.flowHelpVisible = true
        this.$nextTick(function () {
          this.$refs.flowHelp.init()
        })
      }
    }
  }
</script>
